package modeller.databasedesignmodel.relation;

import exceptions.NoIndexOnAttributesException;
import modeller.databasedesignmodel.Attribute;
import modeller.databasedesignmodel.relation.index.AbstractTableIndex;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

/**
 * Created by IntelliJ IDEA.
 * User: Jason Ye
 * Date: 21/01/2012
 * Time: 16:17
 */
public class Relation implements Serializable {

    private int m_expectedNumRows;
    private HashSet<AbstractTableIndex> m_tableIndexes;
    private HashSet<Attribute> m_attributes;
    private double DaverageIOTime = -1;

    private String relationName;
    private double m_DbPageSize;
    private double m_dbFillFactor;
    private double indexPointerSize;

    private HashMap<String, Attribute> attributesFromName = new HashMap<String, Attribute>();

    //private int BnumDBPages = -1;
    //private double FaverageNumChildren = -1;
    //private double RindexEntryToRowSize = -1;

    @Override
    public String toString() {


        StringBuffer stringBuffer = new StringBuffer();

        stringBuffer.append("\n********** RELATION **********\n");

        stringBuffer.append("\nRelation with: "
                + "\n\tExpected number of rows: " + m_expectedNumRows
                + "\n\tExpected row length: " + getExpectedRowSize()
                + "\n\tB - the number of DB pages: " + getBNumDbPages()
                + "\n\tD - average IO time: " + getDAverageIOTime()
        );
        int i = 0;


        // print out fanout and ratio of index to row size for all tree indexes
        for (AbstractTableIndex index : m_tableIndexes) {
            i++;


            if (index.isTree()) {
                stringBuffer.append("\n\tIndex " + i + ": Index fanout for tree index = " + getFIndexTreeFanout(index, m_dbFillFactor));
            }

            stringBuffer.append(
                    "\n\tIndex " + i
                            + ": Ratio of index entry size to row size = "
                            + getRIndexEntryToRowSize(index));
        }

        stringBuffer.append("\n********** ATTRIBUTES **********\n");
        for (Attribute a : m_attributes) {
            stringBuffer.append(a.toString());
        }
        stringBuffer.append("\n****** END OF ATTRIBUTES *******\n");

        stringBuffer.append("\n********** INDEXES **********\n");
        for (AbstractTableIndex index : m_tableIndexes) {
            stringBuffer.append(index.toString());
        }
        stringBuffer.append("\n****** END OF INDEXES *******\n");

        stringBuffer.append("\n********** END OF RELATION **********\n");

        return stringBuffer.toString();

    }

    public Relation() {

        //Relation(0,0,0,)
        m_expectedNumRows = 0;
        m_tableIndexes = new HashSet<AbstractTableIndex>();
    }

    public Relation(int expectedNumRows, double daverageIOTime, double DBpageSize, double dbFillFactor) {
        // use default pointer size for PGSQL
        this(expectedNumRows, daverageIOTime, DBpageSize, dbFillFactor, 6);
    }

    public Relation(int expectedNumRows, double daverageIOTime, double DBpageSize, double dbFillFactor, double indexPointerSize) {

        this.m_expectedNumRows = expectedNumRows;
        this.m_tableIndexes = new HashSet<AbstractTableIndex>();
        this.m_DbPageSize = DBpageSize;
        this.DaverageIOTime = daverageIOTime;
        this.m_dbFillFactor = dbFillFactor;
        this.indexPointerSize = indexPointerSize;

        m_tableIndexes = new HashSet<AbstractTableIndex>();
        m_attributes = new HashSet<Attribute>();

    }

    public int getBNumDbPages() {

        double rowsPerPage = getRowsPerDBPages();

        return (int) Math.ceil(((double)m_expectedNumRows)/rowsPerPage);

    }

    public double getRowsPerDBPages(){
        return Math.floor(m_DbPageSize*m_dbFillFactor/(double)getExpectedRowSize());
    }

    public double getFIndexTreeFanout(AbstractTableIndex index, double dbFillFactor) {


        // ceil(DB page size * index fill factor) / (rowLen))
        return Math.ceil(m_DbPageSize * dbFillFactor / getIndexEntrySize(index));

    }

    private double getIndexEntrySize(AbstractTableIndex index) {
        return ((double) index.getIndexEntrySize()) + indexPointerSize;
    }

    public double getRIndexEntryToRowSize(AbstractTableIndex index) {
        return (getIndexEntrySize(index)) / ((double) getExpectedRowSize());
    }

    public int getExpectedNumRows() {
        return m_expectedNumRows;
    }

    public void setExpectedNumRows(int m_expectedNumRows) {
        this.m_expectedNumRows = m_expectedNumRows;
    }

    /**
     * iterates through the attributes and returns the expected length of a row - assume rows are fully filled
     *
     * @return
     */
    public int getExpectedRowSize() {
        int total = 0;
        for (Attribute a : m_attributes) {
            total += a.getAttributeSize();
        }
        return total;
    }

    public String getRelationName() {
        return relationName;
    }

    public double getDAverageIOTime() {
        return DaverageIOTime;
    }

    public void setDAverageIOTime(double averageIOTime) {
        this.DaverageIOTime = averageIOTime;
    }

    public void setRelationName(String tableName) {
        this.relationName = tableName;
    }

    public HashSet<Attribute> getAttributes() {
        return m_attributes;
    }

    public void setAttributes(HashSet<Attribute> m_attributes) {
        this.m_attributes = m_attributes;
        processAttributeSet();
    }

    private void processAttributeSet() {

        for(Attribute a : m_attributes){
            attributesFromName.put(a.getAttributeName(),a);
        }
    }

    public double getDbFillFactor() {
        return m_dbFillFactor;
    }

    public void setDbFillFactor(double dbFillFactor) {
        this.m_dbFillFactor = dbFillFactor;
    }

    public void addAttribute(Attribute a) {
        m_attributes.add(a);
        attributesFromName.put(a.getAttributeName(),a);
    }

    /**
     * accessor methods for entire set of table indexes on relation **
     */

    public Set<AbstractTableIndex> getTableIndexes() {
        return m_tableIndexes;
    }

    public void setTableIndexes(HashSet<AbstractTableIndex> m_tableIndexes) {
        this.m_tableIndexes = m_tableIndexes;
    }

    /*****************************************************************/

    /**
     * accessor methods for individual table indexes on relation **
     */

    public void addTableIndex(AbstractTableIndex newIndex) {
        m_tableIndexes.add(newIndex);
    }

    public boolean removeTableIndex(AbstractTableIndex indexToRemove) {
        return m_tableIndexes.remove(indexToRemove);
    }

    /**
     * *************************************************************
     * <p/>
     * public AbstractTableIndex getUniqueIndex() {
     * return m_uniqueIndex;
     * }
     */

    public AbstractTableIndex getIndexWithAttributes(HashSet<Attribute> snd) throws NoIndexOnAttributesException {

        for (AbstractTableIndex index : m_tableIndexes) {
            if (index.getAttributeList().containsAll(snd)) {
                return index;
            }
        }

        throw new NoIndexOnAttributesException();

    }

    public int numAttributes(){
        return m_attributes.size();
    }

    public Attribute getAttribute(String attributeName){
        return attributesFromName.get(attributeName);
    }
}
